package com.own.business.question.business.service.impl;

import com.google.common.collect.Maps;
import com.own.business.question.business.service.PracticeHistoryService;
import com.own.business.question.business.service.PracticeMistakeService;
import com.own.business.question.business.service.PracticeService;
import com.own.business.question.business.service.QuestionService;
import com.own.business.question.business.util.PracticeUtil;
import com.own.business.question.entity.bo.PracticeBo;
import com.own.business.question.entity.map.PracticeMapVo;
import com.own.business.question.entity.model.PracticeConfig;
import com.own.business.question.entity.po.Practice;
import com.own.business.question.entity.po.PracticeHistory;
import com.own.business.question.entity.po.PracticeMistake;
import com.own.business.question.entity.query.PracticeQuery;
import com.own.business.question.entity.vo.PracticeVo;
import com.own.business.question.entity.vo.QuestionVo;
import com.own.business.question.mapper.PracticeMapper;
import com.own.component.common.base.service.impl.AbstractBaseService;
import com.own.component.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;

/**
 * PracticeServiceImpl
 *
 * @author chenxueli
 * @date 2021/05/14 15:49:28
 */
@Slf4j
@Service
public class PracticeServiceImpl extends AbstractBaseService<
        Practice,
        PracticeBo,
        PracticeVo,
        PracticeMapVo,
        PracticeQuery,
        PracticeMapper
        > implements PracticeService {

    protected static final Map<Integer, Boolean> ANSWER_MAP = Maps.newHashMapWithExpectedSize(4);

    static {
        ANSWER_MAP.put(Practice.Status.NO_ANSWER, false);
        ANSWER_MAP.put(Practice.Status.ANSWER, true);
        ANSWER_MAP.put(Practice.Status.SAVE, false);
        ANSWER_MAP.put(Practice.Status.ABANDON, false);
    }

    @Resource
    private QuestionService questionService;
    @Resource
    private PracticeHistoryService practiceHistoryService;
    @Resource
    private PracticeMistakeService practiceMistakeService;

    /**
     * 获取练习题目
     *
     * @param targetId 目标id
     * @param userId   获取题目的用户id
     * @param config   练习配置
     * @return 练习信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PracticeVo getPractice(Long targetId, Long userId, PracticeConfig config) {
        // 获取信息
        var wrapper = lambdaQueryWrapper()
                .eq(Practice::getFkTargetId, targetId)
                .eq(Practice::getFkUserId, userId)
                .eq(Practice::getReplyEnvironment, config.getEnvironment().type())
                .orderByDesc(Practice::getGmtCreate);
        var practice = getOneByWrapper(wrapper);
        if (practice != null) {
            // 判断是否已经做完了
            if (practice.getStatus() == Practice.Status.NO_ANSWER || practice.getStatus() == Practice.Status.SAVE || !config.getEnvironment().repeat()) {
                return getById(practice.getId());
            } else {
                return getNewPractice(targetId, userId, config);
            }
        }
        return getNewPractice(targetId, userId, config);
    }

    /**
     * 获取新的练习信息
     *
     * @param targetId 目标id
     * @param userId   获取题目的用户id
     * @param config   练习配置
     * @return 练习信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PracticeVo getNewPractice(Long targetId, Long userId, PracticeConfig config) {
        // 将练习存入数据库
        var practice = new Practice();
        practice.init();
        practice.setFkTargetId(targetId);
        practice.setReplyEnvironment(config.getEnvironment());
        practice.setFkUserId(userId);
        add(practice);
        var questionList = new ArrayList<QuestionVo>();
        config.types().forEach((type, count) -> {
            // 直接获取所有的题目信息
            var tempList = questionService.listByIds(config.getQuestionIdList(), false, true, type, count);
            questionList.addAll(tempList);
        });
        if (questionList.size() == 0) {
            throw new BusinessException("题目数量为0，组题失败");
        }
        // 添加流水信息
        var historyList = new ArrayList<PracticeHistory>();
        // 添加序号
        for (var i = 0; i < questionList.size(); i++) {
            var question = questionList.get(i);
            var history = new PracticeHistory(question, practice, userId);
            history.setSortOrder(i);
            historyList.add(history);
        }
        Collections.shuffle(historyList);
        practiceHistoryService.addPoBatch(historyList);
        // 修改数量
        practice.setQuestionNumber(questionList.size());
        update(practice);
        return new PracticeVo(practice)
                .setHistoryIdList(historyList.stream().map(PracticeHistory::getId).toList());
    }

    /**
     * 根据id获取信息之后处理的信息
     *
     * @param vo vo对象
     * @return 处理的vo对象
     */
    @Override
    public PracticeVo afterGetById(PracticeVo vo) {
        // 获取所有的历史数据信息
        var historyList = practiceHistoryService.list(
                wrapper -> wrapper
                        .eq(PracticeHistory::getFkPracticeId, vo.getId())
                        .orderByAsc(PracticeHistory::getSortOrder)
                        .orderByDesc(PracticeHistory::getGmtCreate)
        );
        vo.setHistoryIdList(historyList.stream().map(PracticeHistory::getId).toList());
        return super.afterGetById(vo);
    }

    /**
     * 确认练习信息（获取成绩）
     *
     * @param pk 练习id
     * @return 练习信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PracticeVo checkPractice(Long pk) {
        var practice = getPoById(pk);
        // 判断是否已经是提交或者废弃的
        switch (practice.getStatus()) {
            case Practice.Status.NO_ANSWER:
            case Practice.Status.SAVE:
                break;
            case Practice.Status.ANSWER:
                throw new BusinessException("当前练习已经确认过了，无法重新确认");
            case Practice.Status.ABANDON:
                throw new BusinessException("当前练习已经被废弃，无法确认练习信息");
            default:
                throw new BusinessException("练习的状态错误：{}", pk + "");
        }
        // 获取所有的历史数据信息
        var historyList = practiceHistoryService.listEqual(PracticeHistory::getFkPracticeId, practice.getId());
        // 获取题目列表
        var mistakeList = new ArrayList<PracticeMistake>();
        var totalScore = historyList.stream()
                // 还原答案信息
                .peek(PracticeUtil::restoreAnswer)
                .map(history -> {
                    var judge = questionService.matchAnswer(history.getFkQuestionId(), history.getAnswer());
                    history.setJudging(judge);
                    history.setStatus(Practice.Status.ANSWER);
                    practiceHistoryService.update(history);
                    var mistake = new PracticeMistake(history);
                    mistakeList.add(mistake);
                    return judge;
                })
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        var score = totalScore.divide(new BigDecimal(historyList.size()), 6, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
        // 设置信息
        var useTime = historyList.stream().map(PracticeHistory::getUseTime).filter(Objects::nonNull).reduce(0L, Long::sum);
        practice.setStatus(Practice.Status.ANSWER);
        practice.setSubmitTime(LocalDateTime.now());
        practice.setScore(score);
        practice.setUseTime(useTime > 0 ? useTime : System.currentTimeMillis() - practice.getRefreshTime().toInstant(ZoneOffset.of("+8")).toEpochMilli());
        update(practice);
        // 插入错题集
        practiceMistakeService.addPoBatch(mistakeList);
        // 重新获取练习数据
        return getById(pk);
    }
}
